// Times out immediately
var maxSigned31BitInt = 1073741823;
// Eventually times out
var IMMEDIATE_PRIORITY_TIMEOUT = -1; 
var USER_BLOCKING_PRIORITY_TIMEOUT = 250;
var NORMAL_PRIORITY_TIMEOUT = 5000;
// Never times out
var LOW_PRIORITY_TIMEOUT = 10000; 
// Tasks are stored on a min heap
var IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt; 

export enum priorityLevel {
  idle = IDLE_PRIORITY_TIMEOUT,
  normal = NORMAL_PRIORITY_TIMEOUT,
  block = USER_BLOCKING_PRIORITY_TIMEOUT,
  low = LOW_PRIORITY_TIMEOUT,
  immediate = IMMEDIATE_PRIORITY_TIMEOUT
}

export type Priority = keyof typeof priorityLevel

export interface Task {
  id: number,
  cb: (value: any) => any,
  priority: Priority,
  startTime: number,
  expirationTime: number,
  sortIndex: number,
  value: any
}

export function push(heap: Array<Task>, node: Task) {
  var index = heap.length;
  heap.push(node);
  siftUp(heap, node, index);
}

export function siftUp(heap: Array<Task>, node: Task, i: number) {
  var index = i;

  while (index > 0) {
    var parentIndex = index - 1 >>> 1;
    var parent = heap[parentIndex];

    if (compare(parent, node) > 0) {
      // The parent is larger. Swap positions.
      heap[parentIndex] = node;
      heap[index] = parent;
      index = parentIndex;
    } else {
      // The parent is smaller. Exit.
      return;
    }
  }
}

export function peek(heap: Array<Task>) {
  return heap.length === 0 ? null : heap[0];
}


export function pop(heap: Array<Task>) {
  if (heap.length === 0) {
    return null;
  }

  var first = heap[0];
  var last = heap.pop() as Task;

  if (last !== first) {
    heap[0] = last;
    siftDown(heap, last, 0);
  }

  return first;
}

export function siftDown(heap: Array<Task>, node: Task, i: number) {
  var index = i;
  var length = heap.length;
  var halfLength = length >>> 1;

  while (index < halfLength) {
    var leftIndex = (index + 1) * 2 - 1;
    var left = heap[leftIndex];
    var rightIndex = leftIndex + 1;
    var right = heap[rightIndex]; // If the left or right node is smaller, swap with the smaller of those.

    if (compare(left, node) < 0) {
      if (rightIndex < length && compare(right, left) < 0) {
        heap[index] = right;
        heap[rightIndex] = node;
        index = rightIndex;
      } else {
        heap[index] = left;
        heap[leftIndex] = node;
        index = leftIndex;
      }
    } else if (rightIndex < length && compare(right, node) < 0) {
      heap[index] = right;
      heap[rightIndex] = node;
      index = rightIndex;
    } else {
      // Neither child is smaller. Exit.
      return;
    }
  }
}

function compare(a: Task, b: Task) {
  var diff = a.sortIndex - b.sortIndex;
  return diff !== 0 ? diff : a.id - b.id
}